#include "globle.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/msg.h>
#include <string.h>
#include <poll.h>
#include "ipc_node.h"
#include "usr_xdma.h"

#define ARRAY_SIZE(x) sizeof(x)/sizeof((x)[0])
#define LOG_TAG "low_speed_port.c :"
//#define USE_PIPE_TO_DO_TEST

#define DATA_DESTINATION_REG  0xa000
#define DATA_LEN_REG          0xa004
#define DATA_PORT_REG         0x9000

#define PORT_DATA_USR_BE_READY(dev) write_bar(dev->usr_config_bar_map_address,0x8018,2)
#define WAIT_FOR_PORT_DATA_READY_IRQ(fd)  is_data_ready(fd)
#define CLR_PORT_DATA_READY_IRQ(dev) write_bar(dev->usr_config_bar_map_address,0x8018,1)
#define GET_DATA_TYPE_PORT_LEN(dev,len,port,type)    do{ \
                                                    type = read_bar(dev->usr_config_bar_map_address, DATA_PORT_REG); \
                                                    /*LOGD("GET_DATA_TYPE_PORT_LEN result 0x%x\r\n",type);*/ \
                                                    len = type & 0xffff; \
                                                    port = ((type >> 16) & 0xffff); \
                                                    /*LOGD("GET_DATA_TYPE_PORT_LEN result len:%d,port:%d\r\n",len,port);*/ \
                                                }while(0)

typedef struct
{
    long int msg_type;
    data_block_str block;
} msg_st_slow_port;

static int msg_tunnel[MSG_ARRAY_SIZE];
static pthread_t port_handle_thread_r = 0, port_handle_thread_w = 0,_low_speed_port_monitor_thread = 0;
static int cmd_port_initialed = 0, low_speed_port_informer_fd = -1;

#ifdef USE_PIPE_TO_DO_TEST
static int debug_named_pipe_fd = -1;
#define DEBUG_NAMED_PIPE_PATH   "/tmp/debug_named_pipe_for_c2h"
#endif

static int port_create_named_pipe(const char *inner_pipe_path)
{
    int ret_val = -1, tmp_fd = -1;
    if (access(inner_pipe_path, F_OK) == 0)
    {
        ret_val = remove(inner_pipe_path);
        if (ret_val < 0)
        {
            LOGE(LOG_TAG"remove named_pipe faild,errno = %d!\r\n", errno);
            return -errno;
        }
    }
    ret_val = mkfifo(inner_pipe_path, 0777);
    if (ret_val < 0)
    {
        LOGE(LOG_TAG"named_pipe initial faild,errno = %d!\r\n", errno);
        return -errno;
    }
    chmod(inner_pipe_path, 0766);
    tmp_fd = open(inner_pipe_path, O_RDWR);
    if (ret_val < 0)
    {
        LOGE(LOG_TAG"open named_pipe faild,errno = %d!\r\n", errno);
        return -errno;
    }
    return tmp_fd;
}

static void port_destroy_named_pipe(const char *inner_pipe_path, int fd)
{
    int ret_val = -1;

    if (fd > 0)
    {
        close(fd);
        fd = -1;
    }

    if (access(inner_pipe_path, F_OK) == 0)
    {
        ret_val = remove(inner_pipe_path);
        if (ret_val < 0)
        {
            LOGE(LOG_TAG"remove named_pipe faild,errno = %d!\r\n", errno);
        }
    }
}

static void port_destroy_msg_queue(int msg_id)
{
    int ret_val = -1;
    if (msg_id < 0)
    {
        LOG(LOG_TAG"msgid < 0,check!\r\n");
    }
    ret_val = msgctl(msg_id, IPC_RMID, 0);
    if (ret_val < 0)
    {
        LOGE(LOG_TAG"msgctl(IPC_RMID) msgid failed,errno = %d\r\n", errno);
    }
}

static int port_generate_msg_queue(const char *path, int key)
{
    key_t tmp_key = -1;
    int tmp_id = -1;

    tmp_key = ftok(path, key);
    if (tmp_key < 0)
    {
        LOGE(LOG_TAG"ftok gen key failed,errno = %d\r\n", errno);
        return -errno;
    }

    tmp_id = msgget(tmp_key, 0666 | IPC_CREAT);
    if (tmp_id < 0)
    {
        LOGE(LOG_TAG"msgget failed,ret_val = %d,errno = %d\r\n",tmp_id,errno);
        return -errno;
    }
    return tmp_id;
}

static unsigned long long int write_pack_count[ENMUM_RESERVE];
static unsigned long long int read_pack_count[ENMUM_RESERVE];
static void *low_speed_port_monitor(void *arg)
{
    int i = 0;
    int pack_speed = 0;
    static unsigned long long int write_pack_count_befor[ENMUM_RESERVE];
    static unsigned long long int read_pack_count_befor[ENMUM_RESERVE];

    xdma_usr_dev *t_dev = (xdma_usr_dev *)arg;
    if (t_dev->monitor_period < 1) t_dev->monitor_period = 2;

    for (i = 0;i < ENMUM_RESERVE;i++)
    {
        write_pack_count_befor[i] = 0;
        read_pack_count_befor[i] = 0;
    }

    while(1)
    {
        sleep(t_dev->monitor_period);
        for (i = 0;i < ENMUM_RESERVE;i++)
        {
            pack_speed = (write_pack_count[i] - write_pack_count_befor[i]) / t_dev->monitor_period;
            LOG("low speed port %03d,write speed %03d packs/s,total %lu packs\r\n",i,pack_speed,write_pack_count[i]);
            write_pack_count_befor[i] = write_pack_count[i];
        }
        LOG("-----------------------------------------------------\r\n");
        for (i = 0;i < ENMUM_RESERVE;i++)
        {
            pack_speed = (read_pack_count[i] - read_pack_count_befor[i]) / t_dev->monitor_period;
            LOG("low speed port %03d,read  speed %03d packs/s,total %lu packs\r\n",i,pack_speed,read_pack_count[i]);
            read_pack_count_befor[i] = read_pack_count[i];
        }
        LOG("\r\n");
    }
}

static int write_map_port_array[ENMUM_RESERVE] = {1,2,3,4,5,10,9,6,7,8,11,12,13,14};
static int low_speed_port_entry[ENMUM_RESERVE] = {0x9000, 0xa000, 0xb000, 0xc000, 0x1000, 0x2000, 0x3000, 0x4000, 0x6000, 0, 0, 0, 0, 0};
static void *port_write_func(void *arg)
{
    xdma_usr_dev *t_dev = (xdma_usr_dev *)arg;
    struct pollfd fds[ENMUM_RESERVE];
    int i = 0, j = 0, ret_val = -1, port = -1, len = -1;
    char t_buff[DEFAULT_MSG_SIZE + 100];
    data_block_str *ptr = (data_block_str *)t_buff;
    msg_st block_msg;    

    LOG("enter port_write_func thread\r\n");

    for (i = 0; i < ENMUM_RESERVE; i++)
    {
        fds[i].fd = msg_tunnel[i + ENMUM_RESERVE];
        fds[i].events = POLLIN;
        write_pack_count[i] = 0;
    }

    while (1)
    {
        //LOGD("polling data......\r\n");
        ret_val = poll(fds, ARRAY_SIZE(fds), -1);
        if (ret_val < 0)
        {
            LOGE(LOG_TAG"port_write_func poll failed,errno = %d\r\n", errno);
            continue;
        }
        //LOGD("port_write_func poll data done,begin to write data to correspond port!\r\n");
        for (i = 0; i < ENMUM_RESERVE; i++)
        {
            if (fds[i].revents)
            {
                ret_val = read(msg_tunnel[i + ENMUM_RESERVE], t_buff, sizeof(data_block_str));
                if (ret_val < 0)
                {
                    LOGE(LOG_TAG"port_write_func read failed,errno = %d\r\n", errno);
                }

                //LOGD(LOG_TAG"ptr->block_type:%d,ptr->block_size:%d,port = %d,read %d data\r\n", ptr->block_type, ptr->block_size,i,ret_val);
#if 0
                LOG("i = %d,snd data:\r\n", i);
                for (j = 0; j < ptr->block_size; j++)
                {
                    if (j > 0 && j % 16 == 0)  LOG("\r\n");
                    LOG("%02x  ", ptr->block[j]);
                }
                LOG("\r\n");
                //LOG("%x  \r\n", ptr->block[543 * 4]);
#endif

                port = write_map_port_array[i]; //port map for hardware designing bug
                switch(ptr->block_type)
                {
                    case PORT_SET_PARAM:
                        port |= (PORT_SET_PARAM << 8);
                        break;
                    case UPDATE_BEGIN:
                        port |= (UPDATE_BEGIN << 8);
                        break;
                    case SND_FIRMWARE_DATA:
                        port |= (SND_FIRMWARE_DATA << 8);
                        break;
                    case SND_FIRMWARE_DONE:
                        port |= (SND_FIRMWARE_DONE << 8);
                        break;
                    default:
                        break;
                }

                //LOGD("%s app-->fpga map src_port %d to map_port %d\r\n",__func__,i + 1,port);
                len = ptr->block_size;
                write_pack_count[i]++;
#if 1
                for (j = 0; j < (len / 4 + (len % 4 ? 1 : 0)); j++)
                {
                    write_bar(t_dev->usr_config_bar_map_address, DATA_DESTINATION_REG, *((uint32_t *)(ptr->block + j * 4)));
                }
                write_bar(t_dev->usr_config_bar_map_address, DATA_PORT_REG, port);
                write_bar(t_dev->usr_config_bar_map_address, DATA_LEN_REG, len);                
                //LOGD("write_pack_count[%d] = %d\r\n",i,write_pack_count[i]);
#else
                LOG("--------------low speed port data write debug,data is not writted to fpga!--------------\r\n");
#endif
            }
        }

    }
}

static int read_map_port_array[ENMUM_RESERVE] = {1,2,3,4,5,8,9,10,7,6,11,12,13,14};
static void *port_read_func(void *arg)
{
    int ret_val = -1, i = 0, len = -1, port = -1,port_map = -1,type = -1;
    xdma_usr_dev *t_dev = (xdma_usr_dev *)arg;
    msg_st_slow_port msg_blk;
    unsigned int *ptr = NULL;    
    LOG("enter port_read_func thread\r\n");

    memset(&msg_blk, '\0', sizeof(msg_st_slow_port));
    memset(&read_pack_count, '\0', sizeof(read_pack_count));

    while (cmd_port_initialed)
    {
#ifdef USE_PIPE_TO_DO_TEST
        ret_val = read(debug_named_pipe_fd, &msg_blk, sizeof(msg_st_slow_port));
        {
            if (ret_val < 0)
            {
                LOGE(LOG_TAG"debug named pipe read failed,errno = %d\r\n", errno);
            }
        }

        len = msg_blk.block.block_size;
        port = msg_blk.msg_type;
        if (len >= DEFAULT_MSG_SIZE)
        {
            LOGE(LOG_TAG"debug payload size should < %d,get %d\r\n", DEFAULT_MSG_SIZE, len);
            continue;
        }

        //LOGD(LOG_TAG"type:0x%x,len:%d,port:%d\r\n",msg_blk.block.block_type,msg_blk.block.block_size,port);

        if (port < 0 || port >= ENMUM_RESERVE)
        {
            LOGE("illegal port number:%d\r\n", port);
            continue;
        }
        msg_blk.msg_type = C2H_MSG_TYPE;
        //continue;
#else
        PORT_DATA_USR_BE_READY(t_dev);
        //LOGD("wait for port data ready irq!\r\n");
        WAIT_FOR_PORT_DATA_READY_IRQ(low_speed_port_informer_fd);
        //LOGD("get event1 irq,begin to read data!\r\n");        
        GET_DATA_TYPE_PORT_LEN(t_dev, len, port,type);
        CLR_PORT_DATA_READY_IRQ(t_dev);
        //something about port,port number is 1-14
        port = port - 1;
        if (port < 0 || port >= ENMUM_RESERVE)
        {
            LOGE("illegal port number:%d\r\n", port);
            continue;
        }
        port_map = read_map_port_array[port] - 1;   //port map for hardware designing bug
        if (len >= DEFAULT_MSG_SIZE)
        {
            LOGE(LOG_TAG"fpga side payload size should < %d,get %d\r\n", DEFAULT_MSG_SIZE, len);
            continue;
        }
        //LOGD("%s fpga-->app map src_port %d to port_map %d\r\n",__func__,port + 1,port_map + 1);
        read_pack_count[port_map]++;
        msg_blk.block.block_type = port_map;
        msg_blk.block.block_size = len;
        msg_blk.msg_type = C2H_MSG_TYPE;
        ptr = (unsigned int *)msg_blk.block.block;

        for (i = 0; i < (len / 4 + (len % 4 ? 1 : 0)); i++)
        {
            *ptr = read_bar(t_dev->usr_config_bar_map_address,DATA_DESTINATION_REG);
            ptr++;
        }
        //LOGD("read_pack_count[%d] = %d\r\n",port_map,read_pack_count[port_map]);
#if 0
        LOG("rcv data:\r\n");
        for (i = 0; i < len; i++)
        {
            if (i > 0 && i % 16 == 0)  LOG("\r\n");
            LOG("%02x  ", msg_blk.block.block[i]);
        }
        LOG("\r\n");
        //LOG("%x  \r\n", msg_blk.block.block[543 * 4]);
#endif
#endif
        //LOGD("-------------------------------DO NOT SEND DATA TO ALGO-------------------\r\n");
        //LOGD(LOG_TAG"send %ld data\r\n",((len % 4) ? (4 * (len / 4 + 1)) : len) + sizeof(msg_blk.block.block_type) + sizeof(msg_blk.block.block_size));
#if 1
#ifdef USE_PIPE_TO_WRITE_DATA
        ret_val = write(msg_tunnel[port_map], &msg_blk, sizeof(msg_st_slow_port));
        if (ret_val < 0)
        {
            LOGE(LOG_TAG"write  msg_tunnel[%d] failed,errno = %d\r\n", port_map, errno);
        }
#else
        ret_val = msgsnd(msg_tunnel[port_map], (const void *)(&msg_blk), ((len % 4) ? (4 * (len / 4 + 1)) : len) + sizeof(msg_blk.block.block_type) + sizeof(msg_blk.block.block_size), 0);
        if (ret_val < 0)
        {
            LOGE(LOG_TAG"msgsnd for low_speed_port_entry[%d] failed,errno = %d\r\n", port_map, errno);
        }
#endif
#else
        LOGD("stop transfering data to user app!\r\n");
#endif
    }
}

void low_speed_port_deinitial(xdma_usr_dev *dev)
{
    int i = 0;
    for (i = 0; i < ENMUM_RESERVE; i++)
    {
#ifdef USE_PIPE_TO_WRITE_DATA
        if (msg_tunnel[i] > 0)
        {
            port_destroy_named_pipe(ucas_msg_path[i], msg_tunnel[i]);
            msg_tunnel[i] = -1;
        }
#else
        if (msg_tunnel[i] > 0)
        {
            port_destroy_msg_queue(msg_tunnel[i]);
            msg_tunnel[i] = -1;
        }
#endif
        if (msg_tunnel[i + ENMUM_RESERVE] > 0)
        {
            port_destroy_named_pipe(ucas_pipe_path[i], msg_tunnel[i + ENMUM_RESERVE]);
            msg_tunnel[i + ENMUM_RESERVE] = -1;
        }
    }

    cmd_port_initialed = 0;
#ifdef USE_PIPE_TO_DO_TEST
    if (debug_named_pipe_fd > 0)
    {
        port_destroy_named_pipe(DEBUG_NAMED_PIPE_PATH, debug_named_pipe_fd);
        debug_named_pipe_fd = -1;
    }
#endif
    if (low_speed_port_informer_fd > 0)
    {
        close(low_speed_port_informer_fd);
        low_speed_port_informer_fd = -1;
    }

    if (port_handle_thread_r > 0)
    {
        //pthread_join(port_handle_thread_r, NULL);
        pthread_cancel(port_handle_thread_r);
        port_handle_thread_r = -1;
    }

    if (port_handle_thread_w > 0)
    {
        //pthread_join(port_handle_thread_w, NULL);
        pthread_cancel(port_handle_thread_w);
        port_handle_thread_w = -1;
    }

    if (_low_speed_port_monitor_thread > 0)
    {
        //pthread_join(_low_speed_port_monitor_thread, NULL);
        pthread_cancel(_low_speed_port_monitor_thread);
        _low_speed_port_monitor_thread = -1;
    }
}

int low_speed_port_initial(xdma_usr_dev *dev)
{
    int i = 0, ret_val = -1;

    if (cmd_port_initialed)
    {
        LOGI("do not reinitial low speed port\r\n");
        return 0;
    }

    cmd_port_initialed = 1;
    for (i = 0; i < MSG_ARRAY_SIZE; i++)
    {
        msg_tunnel[i] = -1;
    }

    for (i = 0; i < ENMUM_RESERVE; i++)
    {
#ifdef USE_PIPE_TO_WRITE_DATA
        msg_tunnel[i] = port_create_named_pipe(ucas_msg_path[i]);//named pipe for c2h
        if (msg_tunnel[i] < 0)
        {
            LOGE(LOG_TAG"port_create_named_pipe for c2h i = %d failed,errno = %d\r\n", i, errno);
            goto port_intial_err;
        }
#else
        if (access(ucas_msg_path[i], F_OK) != 0)
        {
            ret_val = mkdir(ucas_msg_path[i], 0777);
            if (ret_val)
            {
                LOGE(LOG_TAG"mkdir failed for %s\r\n", ucas_msg_path[i]);
                goto port_intial_err;
            }
            chmod(ucas_msg_path[i], 0766);
        }

        msg_tunnel[i] = port_generate_msg_queue(ucas_msg_path[i], i); //msg for c2h
        if (msg_tunnel[i] < 0)
        {
            LOGE(LOG_TAG"port_generate_msg_queue i = %d failed,errno = %d\r\n", i, errno);
            goto port_intial_err;
        }
#endif
        msg_tunnel[i + ENMUM_RESERVE] = port_create_named_pipe(ucas_pipe_path[i]);//named pipe for htc
        if (msg_tunnel[i + ENMUM_RESERVE] < 0)
        {
            LOGE(LOG_TAG"port_create_named_pipe for h2c i = %d failed,errno = %d\r\n", i, errno);
            goto port_intial_err;
        }
    }

    low_speed_port_informer_fd = open(LOW_SPEED_PORT_R_INFORMER_PATH, O_RDWR);
    if (low_speed_port_informer_fd < 0)
    {
        LOGE(LOG_TAG"open %s faild!\r\n", LOW_SPEED_PORT_R_INFORMER_PATH);
        goto port_intial_err;
    }

#ifdef USE_PIPE_TO_DO_TEST
    debug_named_pipe_fd = port_create_named_pipe(DEBUG_NAMED_PIPE_PATH);//named pipe for c2h
    if (debug_named_pipe_fd < 0)
    {
        LOGE(LOG_TAG"port_create_named_pipe failed,errno = %d\r\n", errno);
        goto port_intial_err;
    }
#endif

   // CLR_PORT_DATA_READY_IRQ(dev);
    ret_val = pthread_create_and_setaffinity(&port_handle_thread_r, NULL, port_read_func, dev, 4);
    if (ret_val != 0)
    {
        LOGE(LOG_TAG"pthread_create_and_setaffinity faild for port_read_func,errno = %d!\r\n", errno);
        goto port_intial_err;
    }

    ret_val = pthread_create_and_setaffinity(&port_handle_thread_w, NULL, port_write_func, dev, 5);
    if (ret_val != 0)
    {
        LOGE(LOG_TAG"pthread_create_and_setaffinity faild for port_write_func,errno = %d!\r\n", errno);
        goto port_intial_err;
    }
////////////////
    ret_val = pthread_create_and_setaffinity(&_low_speed_port_monitor_thread, NULL, low_speed_port_monitor, dev, 6);
    if (ret_val != 0)
    {
        LOGE(LOG_TAG"pthread_create_and_setaffinity faild for low_speed_port_monitor,errno = %d!\r\n", errno);
        goto port_intial_err;
    }

    return 0;

port_intial_err:
    low_speed_port_deinitial(dev);
    return -errno;
}

